#pragma once
#include "ReadsMapping.h"
#include "MappingResult.h"
#include "LongReadsSet.h"

class CPairedMappingFlags
{
public:
    CPairedMappingFlags();
    bool bNoMapping;
    bool sepMore;
    bool sepLess;
    bool pairedOnExpStrand;
    bool bPrintAllMapping;
    bool mapSOLiDRead; // TODO: this flag should be constant
};

class CBestPairedMapping
{
public:
    CBestPairedMapping(void);
    ~CBestPairedMapping(void);
    CMappingResult bm1, bm2;
    int bestMappingNo;
    int validMappingNo;
    unsigned int minDiff;
    inline void update(CMappingResult &m1, CMappingResult &m2, bool excludeAmbigousRead);
};

class CPairedReadsMapping : public CReadsMapping
{
public:
    CPairedReadsMapping(void);
    CPairedReadsMapping(const MappingOpts P);
    ~CPairedReadsMapping(void);
    //int mapPairedReadsInASingleFile(CPairedReadsSet& readSet, GIndexTQ& table);
    template <class ReadSetType1, class ReadSetType2>
    int mapPairedReadsInPairedFiles(ReadSetType1& readSet1, ReadSetType2& readSet2, const GIndexTQ& table1, const GIndexTQ& table2);
    int mapPairedReads(CReadInBitsSet& readSet1, CReadInBitsSet& readSet2, const GIndexTQ& table1, const GIndexTQ& table2);
    int mapPairedReads(CLongReadsSet& readSet1, CReadInBitsSet& readSet2, const GIndexTQ& table1, const GIndexTQ& table2);
    int mapPairedReads(CLongReadsSet& readSet1, CLongReadsSet& readSet2, const GIndexTQ& table1, const GIndexTQ& table2);

    int dealMappedSingleRead(const GIndexTQ& table, CAlignmentsQ & Que, CMappingResult &m, bool bFirstEnd);
    int dealMappedPairedReads(const GIndexTQ& table1, const GIndexTQ& table2);
    int dealMappedLongPairedRead(const GIndexTQ& table, const GIndexTQ& table2, CMappingResult& m1, CMappingResult& m2);
    // F3read and R3read are index in AlignmentsQ
    inline int dealMappingResult(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult& m1, CMappingResult& m2, CBestPairedMapping& bestMP, CPairedMappingFlags& flags);
    void dealNoMapping(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult& m1, CMappingResult& m2);
    int dealBestMapping(const GIndexTQ& table1, const GIndexTQ& table2, CBestPairedMapping& bestMP, CMappingResult& m1, CMappingResult& m2);
    int printValidMappedPair(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult& m1, CMappingResult& m2, int validMappedPairNo);
    int printBestMappedPair(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult& m1, CMappingResult& m2, int minMismatchNo, int bestMappedPairNo);
    inline void getMappedPairedRef(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult &m1, CMappingResult &m2, bool samFormat);
    inline void getPairedRInfo(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult &m1, CMappingResult &m2, bool samFormat);
    inline void getPairedRSeqAndQ(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult &m1, CMappingResult &m2, bool samFormat);
    inline void getPairedMappedRef4Solexa(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult &m1, CMappingResult &m2, bool samFormat);
    void printAMappedPair(const GIndexTQ& table1, const GIndexTQ& table2, CMappingResult &m1, CMappingResult &m2, int noPairedLoc);
    void printMappedPairStats(ostream& out, const char* readSetName, unsigned int uiSubThreshold);
protected:
    void initialization(void);
    virtual int printLogFile(const char* inputFile);
    bool bookKeepValidMappedPairs
    (CBestPairedMapping& bestMP, CPairedMappingFlags& flags, CMappingResult &m1, CMappingResult &m2);
    inline void bookNoMappedKeepPairs(bool sepMore, bool sepLess, bool pairedOnExpStrand);
    inline void bookKeepMappedPairs(CBestPairedMapping& bestMP);
    bool isMappingSolidPaired(CLongReadsSet& readSet1, CReadInBitsSet& readSet2, const GIndexTQ& table1, const GIndexTQ& table2);
    int noOfPairsInRange;
    int noOfPairsSepMore;
    int noOfPairsSepLess;
    int noOfPairsSepMoreAndLess;
    int noOfSingle1stEndMapped;
    int noOfSingle2ndEndMapped;
    int noOfAmbiguousPairs;
    int noOfExpMappedPairedStrand;
};

inline void CPairedReadsMapping::bookNoMappedKeepPairs(bool sepMore, bool sepLess, bool pairedOnExpStrand)
{
    if (sepMore && sepLess) {
        noOfPairsSepMoreAndLess++;
    } else if (sepMore) {
        noOfPairsSepMore++;
    } else if (sepLess) {
        noOfPairsSepLess++;
    }
    if (pairedOnExpStrand) {
        this->noOfExpMappedPairedStrand++;
    }
}

inline void CPairedReadsMapping::bookKeepMappedPairs(CBestPairedMapping& bestMP)
{
    this->noOfPairsInRange++;
    this->iMapCount++;
    this->iMapDiffCount[bestMP.minDiff]++;
    bool stricklyExcludeAmbiguous = opt.bExcludeAmbiguousPaired && opt.bGetAllAlignments; // -A -e
    bool bAmbiguous = (stricklyExcludeAmbiguous && bestMP.validMappingNo > 1) || bestMP.bestMappingNo > 1;
    if (bAmbiguous) {
        this->iMultiMappedReads++;
    }
}
inline void getSingleMappingInfo(CMappingResult &m, CAlignmentsQ &q, int mappingIndex);

// Map mated paired reads parallelly
int parallelMapPairedReads(vector<string>& readSetsList1, vector<string>& readSetsList2, GIndexTQ& table1, GIndexTQ& table2, MappingOpts& P);
// For the paired read set in a file with 5'-3' concatenated with 3'-5'.
int parallelMapPairedReads(vector<string>& readSetsList, GIndexTQ& table1, GIndexTQ& table2, MappingOpts& P);

inline bool isLongPairedReads(unsigned int readLength1, unsigned int readLength2)
{
    return(isReadProperLong(readLength1) && isReadProperLong(readLength2));
}

inline bool isLongShortPairedReads(unsigned int readLength1, unsigned int readLength2)
{
    return(isReadProperLong(readLength1) && isReadShort(readLength2));
}

inline bool isShortPairedReads(unsigned int readLength1, unsigned int readLength2)
{
    return(isReadShort(readLength1) && isReadShort(readLength2));
}

template <class ReadSetType1, class ReadSetType2>
void initPairedReadSet(ReadSetType1& set1, ReadSetType2& set2, const char* readSetName1, const char* readSetName2, MappingOpts& P)
{
    set1.init(readSetName1, P.readsFileFormat, P.readLength, P.allowedNumOfNinRead, P.truncatedReadPrefix);
    set2.init(readSetName2, P.readsFileFormat, P.readLength2, P.allowedNumOfNinRead, P.truncatedReadPrefix);
    if (P.bIgnoreQS) {
        set1.ignoreQScores();
        set2.ignoreQScores();
    }
};

template <class ReadSetType1, class ReadSetType2>
int get_next_capacity_paired_reads(ReadSetType1 &set1, ReadSetType2 &set2, unsigned int& iBadReadPairedCount)
{
    bool bStoreQS = set1.bStoreQS() && set2.bStoreQS();
    bool bGetQScores = set1.bGetQScores() && set2.bGetQScores() && bStoreQS;
    bool bSOLiDReadFormat = set1.isSOLiDReadFormat() && set2.isSOLiDReadFormat();
    // clearReads will clear and reserve BUFFERED_READS_SIZE, but will not reset bad read count
    set1.clearReads(BUFFERED_READS_SIZE);
    set2.clearReads(BUFFERED_READS_SIZE);
    do {
        const char* caNextRead1 = set1.get_Next_Read();
        const char* caNextRead2 = set2.get_Next_Read();
        if (caNextRead1[0] == '\0' || caNextRead2[0] == '\0') {
            set1.flushParser();
            set2.flushParser();
            break; // End of the file
        } else if (isBadRead(bSOLiDReadFormat, caNextRead1, set1.getReadLength()) ||
                   isBadRead(bSOLiDReadFormat, caNextRead2, set2.getReadLength())) {
            // handleBadRead() add bad read count in each set.
            set1.handleBadRead();
            set2.handleBadRead();
            iBadReadPairedCount ++;
        } else {
            // TODO: This may not work for long SOLiD read
            bool saveOk1 = set1.save_next_read(bSOLiDReadFormat, bGetQScores);
            bool saveOk2 = set2.save_next_read(bSOLiDReadFormat, bGetQScores);
            if(saveOk1 && saveOk2) {
                // TODO: Consider to add delimiter
                set1.save_next_read_id();
                set2.save_next_read_id();
            }
        }
    } while (set1.size() < set1.capacity() && set2.size() < set2.capacity());
    printf("Deal read no. %u in %s.\r", set1.uiNo_of_Reads, set1.InputFile);
    set1.removeExtraTags();
    set2.removeExtraTags();
    if(bStoreQS) {
        set1.getQualityScoresFromQUAL();
        set2.getQualityScoresFromQUAL();
    }

    return((unsigned int)min(set1.size(), set2.size()));
};

template <class ReadSetType1, class ReadSetType2>
int CPairedReadsMapping::mapPairedReadsInPairedFiles\
(ReadSetType1& set1, ReadSetType2& set2, const GIndexTQ& table1, const GIndexTQ& table2)
{
    const char* readSetName = set1.readSetName();
    const char* readSetName2 = set2.readSetName();
    string seedStr = seedSymbol(table1.chosenSeedId);
    string seedStr2 = seedSymbol(table1.chosenSeedId);
    getReadsFileFormat(readSetName, opt.readsFileFormat);
    printf("Mapping %s & %s (%u,%u-bp reads) with %s,%s seed.\n", \
           readSetName, readSetName2, set1.getReadLength(), set2.getReadLength(), seedStr.c_str(), seedStr2.c_str());
    this->initializeStatsCounter();
    if (this->setUpIO4Aligment(set1.InputFile, table1) != 0) {
        LOG_INFO("\nInfo %d: Fail to setup I/O files.", ERROR_LOG);
    }
    // TODO opt.readtag_delimiter is not passed in to get the name of read as the single end read parser
    while(get_next_capacity_paired_reads(set1, set2, this->iBadReadCounter) > 0) {
        mapPairedReads(set1, set2, table1, table2);
    }
    this->tearDownIO4Aligment();
    this->printLogFile(readSetName);
    return(0);
}
